/* * Copyright 2015 Daniel Gredler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package net.gredler.testfs; import static net.gredler.testfs.Permissions.RWX; import static net.gredler.testfs.Permissions.RW_; import static net.gredler.testfs.Permissions.R_X; import static net.gredler.testfs.Permissions.R__; import static net.gredler.testfs.Permissions._WX; import static net.gredler.testfs.Permissions._W_; import static net.gredler.testfs.Permissions.__X; import java.io.IOException; import java.net.URI; import java.nio.channels.SeekableByteChannel; import java.nio.file.AccessDeniedException; import java.nio.file.AccessMode; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream.Filter; import java.nio.file.FileStore; import java.nio.file.FileSystem; import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.nio.file.spi.FileSystemProvider; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * {@link FileSystemProvider} wrapper. */ final class TestFileSystemProvider extends FileSystemProvider { /** The wrapped file system provider. */ private final FileSystemProvider provider; /** Paths that don't exist in this file system provider. */ private final List< Path > removedPaths; /** Targets for additional paths that only exist in this file system provider. */ private final Map< Path, Path > addedPathTargets; /** Permissions for additional paths that only exist in this file system provider. */ private final Map< Path, Permissions > addedPathPermissions; /** Access to these files using these access modes should trigger an {@link IOException}. */ private final Map< Path, Set< AccessMode > > exceptionPaths; /** * Creates a new instance. * * @param fs the wrapped file system * @param removedPaths paths that don't exist in this file system provider * @param addedPathTargets targets for additional paths that only exist in this file system provider * @param addedPathPermissions permissions for additional paths that only exist in this file system provider * @param exceptionPaths access to these files using these access modes should trigger an {@link IOException} */ TestFileSystemProvider(FileSystem fs, List< String > removedPaths, Map< String, String > addedPathTargets, Map< String, Permissions > addedPathPermissions, Map< String, Set< AccessMode > > exceptionPaths) { this.provider = fs.provider(); this.removedPaths = new ArrayList< Path >(); for (String removedPath : removedPaths) { this.removedPaths.add(fs.getPath(removedPath).toAbsolutePath()); } this.addedPathTargets = new HashMap<>(); for (Map.Entry< String, String > addedPath : addedPathTargets.entrySet()) { this.addedPathTargets.put(fs.getPath(addedPath.getKey()).toAbsolutePath(), fs.getPath(addedPath.getValue()) .toAbsolutePath()); } this.addedPathPermissions = new HashMap<>(); for (Map.Entry< String, Permissions > addedPathPermission : addedPathPermissions.entrySet()) { this.addedPathPermissions.put(fs.getPath(addedPathPermission.getKey()).toAbsolutePath(), addedPathPermission.getValue()); } this.exceptionPaths = new HashMap<>(); for (Map.Entry< String, Set< AccessMode > > entry : exceptionPaths.entrySet()) { this.exceptionPaths.put(fs.getPath(entry.getKey()).toAbsolutePath(), entry.getValue()); } } /** {@inheritDoc} */ @Override public String getScheme() { return provider.getScheme(); } /** {@inheritDoc} */ @Override public FileSystem newFileSystem(URI uri, Map< String, ? > env) throws IOException { return new TestFileSystem(provider.newFileSystem(uri, env), this); } /** {@inheritDoc} */ @Override public FileSystem getFileSystem(URI uri) { return new TestFileSystem(provider.getFileSystem(uri), this); } /** {@inheritDoc} */ @Override public Path getPath(URI uri) { return TestPath.wrap(provider.getPath(uri), new TestFileSystem(provider.getFileSystem(uri), this)); } /** {@inheritDoc} */ @Override public SeekableByteChannel newByteChannel(Path path, Set< ? extends OpenOption > options, FileAttribute< ? >... attrs) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); checkPermissions(path, true, toAccessModes(options)); Path target = addedPathTargets.get(path.toAbsolutePath()); if (target != null) { return provider.newByteChannel(target, options, attrs); } return provider.newByteChannel(path, options, attrs); } private static AccessMode[] toAccessModes(Set< ? extends OpenOption > options) { boolean write = options.contains(StandardOpenOption.WRITE); boolean append = options.contains(StandardOpenOption.APPEND); List< AccessMode > modes = new ArrayList<>(); if (write || append) { modes.add(AccessMode.WRITE); } else { modes.add(AccessMode.READ); } return modes.toArray(new AccessMode[modes.size()]); } /** {@inheritDoc} */ @Override public DirectoryStream< Path > newDirectoryStream(Path dir, Filter< ? super Path > filter) throws IOException { dir = ((TestPath) dir).unwrap(); checkIfRemoved(dir); checkPermissions(dir, true, AccessMode.READ); return provider.newDirectoryStream(dir, filter); } /** {@inheritDoc} */ @Override public void createDirectory(Path dir, FileAttribute< ? >... attrs) throws IOException { dir = ((TestPath) dir).unwrap(); Path parent = dir.getParent(); if (parent != null) { checkIfRemoved(parent); checkPermissions(parent, true, AccessMode.WRITE); } provider.createDirectory(dir, attrs); } /** {@inheritDoc} */ @Override public void delete(Path path) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); Path parent = path.getParent(); if (parent != null) { checkPermissions(parent, true, AccessMode.WRITE); } provider.delete(path); } /** {@inheritDoc} */ @Override public void copy(Path source, Path target, CopyOption... options) throws IOException { source = ((TestPath) source).unwrap(); target = ((TestPath) target).unwrap(); checkIfRemoved(source); checkPermissions(source, true, AccessMode.READ); provider.copy(source, target, options); } /** {@inheritDoc} */ @Override public void move(Path source, Path target, CopyOption... options) throws IOException { source = ((TestPath) source).unwrap(); target = ((TestPath) target).unwrap(); checkIfRemoved(source); Path sourceParent = source.getParent(); if (sourceParent != null) { checkIfRemoved(sourceParent); checkPermissions(sourceParent, true, AccessMode.WRITE); } Path targetParent = target.getParent(); if (targetParent != null) { checkIfRemoved(targetParent); checkPermissions(targetParent, true, AccessMode.WRITE); } provider.move(source, target, options); } /** {@inheritDoc} */ @Override public boolean isSameFile(Path path, Path path2) throws IOException { path = ((TestPath) path).unwrap(); path2 = ((TestPath) path2).unwrap(); return provider.isSameFile(path, path2); } /** {@inheritDoc} */ @Override public boolean isHidden(Path path) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); return provider.isHidden(path); } /** {@inheritDoc} */ @Override public FileStore getFileStore(Path path) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); return provider.getFileStore(path); } /** {@inheritDoc} */ @Override public void checkAccess(Path path, AccessMode... modes) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); boolean checked = checkPermissions(path, false, modes); if (!checked) { provider.checkAccess(path, modes); } } /** {@inheritDoc} */ @Override public < V extends FileAttributeView > V getFileAttributeView(Path path, Class< V > type, LinkOption... options) { path = ((TestPath) path).unwrap(); try { checkIfRemoved(path); } catch (NoSuchFileException e) { return null; } return provider.getFileAttributeView(path, type, options); } /** {@inheritDoc} */ @Override public < A extends BasicFileAttributes > A readAttributes(Path path, Class< A > type, LinkOption... options) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); return provider.readAttributes(path, type, options); } /** {@inheritDoc} */ @Override public Map< String, Object > readAttributes(Path path, String attributes, LinkOption... options) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); return provider.readAttributes(path, attributes, options); } /** {@inheritDoc} */ @Override public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { path = ((TestPath) path).unwrap(); checkIfRemoved(path); provider.setAttribute(path, attribute, value, options); } /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (obj instanceof TestFileSystemProvider) { TestFileSystemProvider other = (TestFileSystemProvider) obj; return other.provider.equals(this.provider) && other.removedPaths.equals(this.removedPaths) && other.addedPathTargets.equals(this.addedPathTargets) && other.addedPathPermissions.equals(this.addedPathPermissions); } else { return false; } } /** {@inheritDoc} */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + provider.hashCode(); result = prime * result + removedPaths.hashCode(); result = prime * result + addedPathTargets.hashCode(); result = prime * result + addedPathPermissions.hashCode(); return result; } /** {@inheritDoc} */ @Override public String toString() { return getClass().getSimpleName() + "[scheme=" + getScheme() + "]"; } private void checkIfRemoved(Path path) throws NoSuchFileException { String originalPath = path.toString(); for (; path != null; path = path.getParent()) { if (removedPaths.contains(path.toAbsolutePath())) { throw new NoSuchFileException(originalPath); } } } private boolean checkPermissions(Path path, boolean checkExceptions, AccessMode... modes) throws AccessDeniedException, IOException { Permissions permissions = addedPathPermissions.get(path.toAbsolutePath()); boolean hasPermissions = permissions != null; if (hasPermissions) { for (AccessMode mode : modes) { switch (mode) { case READ: if (permissions != RWX && permissions != R_X && permissions != RW_ && permissions != R__) { throw new AccessDeniedException(path.toString()); } break; case WRITE: if (permissions != RWX && permissions != _WX && permissions != RW_ && permissions != _W_) { throw new AccessDeniedException(path.toString()); } break; case EXECUTE: if (permissions != RWX && permissions != _WX && permissions != R_X && permissions != __X) { throw new AccessDeniedException(path.toString()); } break; default: // impossible } } } if (checkExceptions) { checkExceptions(path, modes); } return hasPermissions; } private void checkExceptions(Path path, AccessMode... modes) throws IOException { Set< AccessMode > errorModes = exceptionPaths.get(path.toAbsolutePath()); if (errorModes != null) { for (AccessMode mode : modes) { if (errorModes.contains(mode)) { throw new IOException(path.toString()); } } } } }